我们在前面的文章分析了Vertx核心单机部分的源码。今天轮到Vert.x-Web,由于Web的内容比较多,因此分为上下两部分。

  • 上:分析Vert.x-Web核心类及其主要能力。
  • 下:分析HttpServer的线程调度以及Web OpenAPI的工作原理

一个基本的VertxWeb代码片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val vertx = Vertx.vertx()
val router = Router.router(vertx)
router.get("/hello")
.handler { ctx ->
println("哈哈哈,我好开心")
ctx.next()
}
.failureHandler(ErrorHandler.create())
val server = vertx.createHttpServer().requestHandler(router)
server.listen(8080)
}

上面创建了一个Http服务,暴露8080端口,并注册了一个path为hello,方法为GET的接口。上面涉及到Vert.x-Web的类有:Router、Route、RoutingContext、HttpServer。我们介绍前三个。

Router

Router

首先是Router接口,它主要有以下几个方面组成。

  • Router.router(vertx) 创建一个Router对象

  • routeXXX() 各种路由方法,可以根据路径、方法、媒体类型、正则表达式等各种方式路由,返回一个Route对象,用于指定处理器。

  • getRoutes() 获取所有route

  • clear() 清除所有Route

  • mountSubRouter() 挂载子router

  • exceptionHandler() 指定一个router级别的异常处理器,当handler中抛出异常时,它会捕获。但它不会影响正常业务逻辑。

    但它目前已被废弃,应该使用errorHandler(),它对应的是500的错误

  • errorHandler(code, handler) 当发生特定错误码时会调用它。当RoutingContext失败(fail) 或 一些handler失败但没有写响应 或 handler中抛出了异常,都会调用它。需要注意的是它的500有特殊的意义,代表通用错误。

  • handleContext(context) 将一个RoutingContext传进来,当一个Router被挂载到某个route时会用:将该route的RoutingContext传入本router进行处理。

  • handleFailure(RoutingContext) 使用场景同上,处理失败

  • modifiedHandler(handler) 当本Router的Route发生变化时该方法会被触发。

首先说,Router只是Handler的子类,因此本质上还只是一个处理器,是被动调用的。

1
2
3
@VertxGen
public interface Router extends Handler<HttpServerRequest> {
... ...

RouterImpl

众多route生成方法的实现都大同小异,都是创建RouteImpl

1
2
3
4
public synchronized Route route(String path) {
state = state.incrementOrderSequence();
return new RouteImpl(this, state.getOrderSequence(), path);
}

另外一个重点是handle,既然Router是Handler的子类,因此本类最初被调用的一定是handle方法(在请求进来时调用

1
2
3
4
5
@Override
public void handle(HttpServerRequest request) {
// 注意后面的next()调用,这很容易被看漏掉。
new RoutingContextImpl(null, this, request, state.getRoutes()).next();
}

啥也没干,就创建了一个RoutingContextImpl,并调用了next(),开启处理逻辑

RouterState

一个RouterImpl包含一个RouterState,在初始化时创建,用于管理Router的状态

1
2
3
4
public RouterImpl(Vertx vertx) {
this.vertx = vertx;
this.state = new RouterState(this);
}

共有如下几种状态

  • Route集合:Router内注册的Route全在这里
  • errorHandler映射:失败的处理器,其中key对应的错误码,如400
  • modifiedHandler:用于在Router发生变化时做出响应的处理器
1
2
3
private final Set<RouteImpl> routes;
private final Map<Integer, Handler<RoutingContext>> errorHandlers;
private final Handler<Router> modifiedHandler;

Route

Route

一个接口,对路由信息的描述,包含如下几种信息,见名思义。

  • method
  • path
  • regex
  • consumes
  • produces

同时包含了对匹配到的请求的处理方式

  • handler():为Route添加一个处理器,多个handler之间会连缀调用。
  • failureHandler():为Route添加一个失败处理器,多个handler之间会连缀调用。
  • blockingHandler():为Route添加一个能够执行阻塞操作的处理器。
  • subRouter():为Route添加一个子Router,符合要求的请求都会被转发给该Router。

RouteImpl

就是对上面各个方法的实现,这里不赘述。

RouteState

对一个路由状态的持有,相对RouterState而言,它持有的状态复杂得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 路径
private final String path;
// 顺序
private final int order;
// 是否开启
private final boolean enabled;
// 方法
private final Set<HttpMethod> methods;
// 消费的多媒体类型
private final Set<MIMEHeader> consumes;
// 是否允许body为空
private final boolean emptyBodyPermittedWithConsumes;
// 产生的多媒体类型
private final Set<MIMEHeader> produces;
// 常规处理器集合
private final List<Handler<RoutingContext>> contextHandlers;
// 失败处理器集合
private final List<Handler<RoutingContext>> failureHandlers;
// 不知道干嘛的
private final boolean added;
// 用于匹配的正则
private final Pattern pattern;
// 也属于正则表达式的一部分
private final List<String> groups;
// 是否使用归一化路径
private final boolean useNormalisedPath;
// 还是正则表达式
private final Set<String> namedGroupsInRegex;
// 主机匹配
private final Pattern virtualHostPattern;
// 路径是否以斜杠结尾
private final boolean pathEndsWithSlash;
// 是否被排除
private final boolean exclusive;
// 是否精确匹配
private final boolean exactPath;

对Route最重要的方法当然是判断是否匹配,该方法也是在RouteState中给出

io.vertx.ext.web.impl.RouteState#matches,逻辑比较复杂,有兴趣可以去看看

RoutingContext

RoutingContext是Router上下文的抽象,代表一个请求从进入到响应全阶段的状态。由于在Router.handle()中,直接创建了RoutingContextImpl对象,并调用了RoutingContext.next(),因此我们重点关注它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// RoutingContext的实现类RoutingContextImpl的构造方法
public RoutingContextImpl(String mountPoint, RouterImpl router, HttpServerRequest request, Set<RouteImpl> routes) {
super(mountPoint, request, routes);
this.router = router;
this.fillParsedHeaders(request);
if (request.path().length() == 0) {
this.fail(400);
} else if (request.path().charAt(0) != '/') {
this.fail(404);
}
}
// RoutingContextImpl的父类RoutingContextImplBase的构造方法
RoutingContextImplBase(String mountPoint, HttpServerRequest request, Set<RouteImpl> routes) {
this.mountPoint = mountPoint;
this.request = new HttpServerRequestWrapper(request);
this.routes = routes;
this.iter = routes.iterator();
this.currentRouteNextHandlerIndex = new AtomicInteger(0);
this.currentRouteNextFailureHandlerIndex = new AtomicInteger(0);
this.resetMatchFailure();
}

// next方法
public void next() {
if (!this.iterateNext()) {
this.checkHandleNoMatch();
}
}

仔细看iterateNext()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
boolean iterateNext() {
boolean failed = this.failed();
// 在route的第二个handler调用next时,走的是这段逻辑。
if (this.currentRoute != null) {
try {
if (!failed && this.currentRoute.hasNextContextHandler(this)) {
this.currentRouteNextHandlerIndex.incrementAndGet();
this.resetMatchFailure();
this.currentRoute.handleContext(this);
return true;
}

if (failed && this.currentRoute.hasNextFailureHandler(this)) {
this.currentRouteNextFailureHandlerIndex.incrementAndGet();
this.currentRoute.handleFailure(this);
return true;
}
} catch (Throwable var5) {
this.handleInHandlerRuntimeFailure(this.currentRoute.getRouter(), failed, var5);
return true;
}
}

// 死循环迭代所有route
while(true) {
// this.iter是Router.getRoutes().iterator()得到的,即迭代所有routes
if (this.iter.hasNext()) {
// RouteState包含了所有Route的状态和内容,因此要操作Route就一定要获取它啦
RouteState routeState = ((RouteImpl)this.iter.next()).state();
this.currentRouteNextHandlerIndex.set(0);
this.currentRouteNextFailureHandlerIndex.set(0);

try {
// 匹配结果是0或4开头的状态码,如果是0则表示成功。这意味着每个route都会匹配一次,也一定会有一个结果。
int matchResult = routeState.matches(this, this.mountPoint(), failed);
// 匹配失败的情况
if (matchResult != 0) {
if (matchResult == 405) {
if (this.matchFailure == 404) {
this.matchFailure = matchResult;
}
} else if (matchResult != 404) {
this.matchFailure = matchResult;
}
continue;
}

// 匹配成功的情况
this.resetMatchFailure();

try {
this.currentRoute = routeState;、

if (failed && this.currentRoute.hasNextFailureHandler(this)) {
// 如果有失败,则调用route原先保存的failureHandler
this.currentRouteNextFailureHandlerIndex.incrementAndGet();
routeState.handleFailure(this);
} else {
// 如果成功,则调用route原先保存的contextHandler
if (!this.currentRoute.hasNextContextHandler(this)) {
continue;
}

this.currentRouteNextHandlerIndex.incrementAndGet();
routeState.handleContext(this);
}
} catch (Throwable var6) {
this.handleInHandlerRuntimeFailure(routeState.getRouter(), failed, var6);
}

return true;
} catch (Throwable var7) {
if (LOG.isTraceEnabled()) {
LOG.trace("IllegalArgumentException thrown during iteration", var7);
}

if (!this.response().ended()) {
this.unhandledFailure(var7 instanceof IllegalArgumentException ? 400 : -1, var7, routeState.getRouter());
}

return true;
}
}

return false;
}
}

再来仔细看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void checkHandleNoMatch() {
if (this.failed()) {
// 如果失败了,则按照未处理的异常处理,即从router的errorHandler中获取对应code的handler并给出结果
this.unhandledFailure(this.statusCode, this.failure, this.router);
} else {
// 没有失败时也获取以下,有说明是预期的失败,还是要处理。
Handler<RoutingContext> handler = this.router.getErrorHandlerByStatusCode(this.matchFailure);
this.statusCode = this.matchFailure;
if (handler == null) {
this.response().setStatusCode(this.matchFailure);
// 404的情况
if (this.request().method() != HttpMethod.HEAD && this.matchFailure == 404) {
this.response().putHeader(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=utf-8").end("<html><body><h1>Resource not found</h1></body></html>");
} else {
// 常规情况,直接结束
this.response().end();
}
} else {
handler.handle(this);
}
}

}

总结

到现在,抛开Router是被谁调用这问题。我们知道整个RoutingContext的执行起始是Router的handle方法:它创建RoutingContextImpl对象,并调用next()启动route的匹配操作,并在匹配后调用对应route的正常handler链或失败handler链。

这里掌握三个关键点

  • RoutingContext对象是在Router的handle中创建的,并在匹配到的route的handler中流转。
  • 遍历所有route的过程会在每次请求进来被处理。逻辑在iterateNext()方法的下半部分。
  • route的handler链调用比较特殊,需要开发者手动调用RoutingContext的next()方法处理。逻辑在iterateNext()方法的上半部分。
  • 在从Router的handle开始,都是在当前线程不阻塞地执行,可以一镜到底。

对于Web的整个处理链,我们弄得差不多了。现在还存在如下问题,我们留待下篇再来看。

  • 谁构建HttpServerRequest并调用了Router的handler?
  • 调用Router时线程分配是如何的?

留言

2020-11-03

⬆︎TOP